/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    tess.cpp
*  @brif:    tess-shader
*
*  @date:    06/03/2021
*  @Author:  Otto.liu
*
******************************************************************************/
#include <string>
#include <cstdlib>
#include <vector>
#include <array>
#include <memory>
#include "vmath.h"
#include "glad/gl-headers.h"

#include <model.h>
#include "glslprogram.h"
#include "texture.h"
#include "node.h"
#include "objectfactory.h"
#include "window.h"

class Msaa: public Node
{
public:
    Msaa();
protected:
    bool startup();
    void render(double currentTime, double difTime);
    void shutdown();
protected:
    GLSLProgram program;
    GLuint vao;
    GLuint vbo;

    std::vector<GLfloat> patchData = {
           -1.0f,  -1.0f, 0.0f,
           -0.33f, -1.0f, 0.0f,
            0.33f, -1.0f, 0.0f,
            1.0f,  -1.0f, 0.0f,

           -1.0f,  -0.33f, 0.0f,
           -0.33f, -0.33f, 0.0f,
            0.33f, -0.33f, 0.0f,
            1.0f,  -0.33f, 0.0f,

           -1.0f,  0.33f, 0.0f,
           -0.33f, 0.33f, 0.0f,
            0.33f, 0.33f, 0.0f,
            1.0f,  0.33f, 0.0f,

           -1.0f,  1.0f, 0.0f,
           -0.33f, 1.0f, 0.0f,
            0.33f, 1.0f, 0.0f,
            1.0f,  1.0f, 0.0f
        };
};

Msaa::Msaa()
{
    nodeName = "Msaa";
}

bool Msaa::startup()
{
    //glPatchParameteri added in 4.0
    //glMapBufferRange added in 3.0
    if (PerfWindow::glVersion < 400) {
        printf("Failed! The GL version is less than 4.0 !\n");
        return false;
    }

    if (!program.build("../media/shaders/msaa/msaa.vs.glsl", "../media/shaders/msaa/msaa.fs.glsl",
                       nullptr, "../media/shaders/msaa/msaa.tc.glsl", "../media/shaders/msaa/msaa.te.glsl"))
        return false;

    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    return true;
}

void Msaa::render(double currentTime, double difTime)
{
    (void)difTime;

    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    static const GLfloat black[] = { 0.1f, 0.1f, 0.6f, 0.0f };
    static const GLfloat one[] = { 1.0f };
    glClearBufferfv(GL_COLOR, 0, black);
    glClearBufferfv(GL_DEPTH, 0, one);

    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*patchData.size(), NULL,
                 GL_DYNAMIC_DRAW); // GL_STATIC_DRAW);

    vmath::vec3 *p = (vmath::vec3 *)glMapBufferRange(GL_ARRAY_BUFFER, 0,
                                                     sizeof(GLfloat) * patchData.size(),
                                                     GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
    memcpy(p, patchData.data(), sizeof(GLfloat)*patchData.size());

    for (int i = 0; i < 16; i++) {
        float fi = (float)i / 16.0f;
        p[i][2] = sinf(currentTime * (0.2f + fi * 0.3f));
    }

    glUnmapBuffer(GL_ARRAY_BUFFER);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    glPatchParameteri(GL_PATCH_VERTICES, 16);

    vmath::mat4 projMatrix = vmath::perspective(45.0f, getWindowRatio(), 1.0f, 100.0f);
    vmath::mat4 mvMatrix =  vmath::translate(0.0f, 0.0f,
                                              -4.0f) * vmath::rotate((float)currentTime * 17.0f, 0.0f, 1.0f, 0.0f)
                             * vmath::rotate((float)currentTime * 10.0f, 1.0f, 0.0f, 0.0f);

    program.use();

    GLint loc = glGetUniformLocation(program.getProgram(), "mv_matrix");
    if (loc >= 0) {
        glUniformMatrix4fv(loc, 1, GL_FALSE, mvMatrix);
    }
    loc = -1;
    loc = glGetUniformLocation(program.getProgram(), "proj_matrix");
    if (loc >= 0) {
        glUniformMatrix4fv(loc, 1, GL_FALSE, projMatrix);
    }
#ifndef USE_GLES
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif

    glViewport(getWidth() >> 1, 0, getWidth() >> 1, getHeight());
    glDrawArrays(GL_PATCHES, 0, 16);

    glViewport(0, 0, getWidth() >> 1, getHeight());
#ifndef USE_GLES
    /* glEnable(GL_MULTISAMPLE) is not supported on GLES for now.
     * GLES specify GL_SAMPLES when create context. */
    glEnable(GL_MULTISAMPLE);
#endif
    glDrawArrays(GL_PATCHES, 0, 16);
#ifndef USE_GLES
    glDisable(GL_MULTISAMPLE);
#endif

    glDisable(GL_DEPTH_TEST);
}

void Msaa::shutdown()
{
    program.clear();

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glDisable(GL_DEPTH_TEST);

    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
}

REGISTER_OBJECT(Node, Msaa)

